/*
 *
 * This file is part of the open-source SeetaFace engine, which includes three modules:
 * SeetaFace Detection, SeetaFace Alignment, and SeetaFace Identification.
 *
 * This file is an example of how to use SeetaFace engine for face alignment, the
 * face alignment method described in the following paper:
 *
 *
 *   Coarse-to-Fine Auto-Encoder Networks (CFAN) for Real-Time Face Alignment, 
 *   Jie Zhang, Shiguang Shan, Meina Kan, Xilin Chen. In Proceeding of the
 *   European Conference on Computer Vision (ECCV), 2014
 *
 *
 * Copyright (C) 2016, Visual Information Processing and Learning (VIPL) group,
 * Institute of Computing Technology, Chinese Academy of Sciences, Beijing, China.
 *
 * The codes are mainly developed by Jie Zhang (a Ph.D supervised by Prof. Shiguang Shan)
 *
 * As an open-source face recognition engine: you can redistribute SeetaFace source codes
 * and/or modify it under the terms of the BSD 2-Clause License.
 *
 * You should have received a copy of the BSD 2-Clause License along with the software.
 * If not, see < https://opensource.org/licenses/BSD-2-Clause>.
 *
 * Contact Info: you can send an email to SeetaFace@vipl.ict.ac.cn for any problems.
 *
 * Note: the above information must be kept whenever or wherever the codes are used.
 *
 */

#include <cstdint>
#include <fstream>
#include <iostream>
#include <string>
#include <common.h>

#include "cv.h"
#include "highgui.h"
#include "opencv2/opencv.hpp"

#include "face_detection.h"
#include "face_alignment.h"

#ifdef _WIN32
std::string DATA_DIR = "../../data/";
std::string MODEL_DIR = "../../model/";
#else
//std::string DATA_DIR = "../data/";
//std::string MODEL_DIR = "../model/";
#endif

void cut(cv::Mat& img, cv::Mat& dst, cv::Rect roi)
{
    int width = cvRound((float)roi.width);
    int height = cvRound((float)roi.height);
    int x = roi.x - (width - roi.width) / 2;
    int y = roi.y - (height - roi.height) / 2;

    cv::Mat mat(height, width, img.type(), cv::Scalar(0, 0, 0));
    int xt = x;
    int yt = y;
    if (x < 0)
    {
        xt = 0;
        width = width + x;
    }

    if (xt + width > img.cols)
        width = img.cols;

    if (y < 0)
    {
        yt = 0;
        height = height + y;
    }

    if (yt + height > img.rows)
        height = img.rows;

    img(cv::Rect(xt, yt, width, height)).copyTo(mat(cv::Rect(xt - x, yt - y, width, height)));
    mat.copyTo(dst);
}

void rotate(cv::Mat& Face, cv::Mat& aligned, std::vector<float>& pts)
{
    cv::Point2f center_eye((pts[0] + pts[2]) / 2 , (pts[1] + pts[3]) / 2 );
    cv::Point2f center_mouth((pts[6] + pts[8]) / 2 , (pts[7] + pts[9]) / 2);
    cv::Point2f half_square(0.5f * Face.cols, 0.25f * Face.rows);

    float distance_x = center_eye.x - half_square.x;
    float distance_y = center_eye.y - half_square.y;
    float distance_me = (float)sqrt((center_mouth.x - center_eye.x) * (center_mouth.x - center_eye.x) + (center_mouth.y - center_eye.y) * (center_mouth.y - center_eye.y));
    float scale = distance_me / (Face.rows * 0.5f);
    cv::Rect margin_rect(0, 0, Face.cols, Face.rows);
    margin_rect.x += (int)distance_x;
    margin_rect.y += (int)distance_y;

    double tan = (pts[1] - pts[3]) / (pts[0] + pts[2]);
    double arctan = atan(tan) * 180 / CV_PI;

    cv::Mat rot_mat = cv::getRotationMatrix2D(cv::Point2f(half_square.x + margin_rect.x, half_square.y + margin_rect.y), -3 * arctan, 1.0);
    cv::Mat roted_img;
    cv::warpAffine(Face, roted_img, rot_mat, Face.size(), cv::INTER_CUBIC, cv::BORDER_CONSTANT, cv::Scalar::all(0));

    margin_rect.x = margin_rect.x + (int)((1 - scale) * margin_rect.width * 0.5f);
    margin_rect.y = margin_rect.y + (int)((1 - scale) * margin_rect.height * 0.25f);
    margin_rect.width = (int)(margin_rect.width * scale);
    margin_rect.height = (int)(margin_rect.height * scale);
    if(margin_rect.x + margin_rect.width >= roted_img.cols || margin_rect.y + margin_rect.height >= roted_img.rows)
    {
        margin_rect.width = roted_img.cols - margin_rect.x;
        margin_rect.height = roted_img.rows - margin_rect.y;
        roted_img(margin_rect).copyTo(aligned);
    }
    else
    {
        roted_img(margin_rect).copyTo(aligned);
    }
    cv::resize(aligned, aligned, cv::Size(256, 256), cv::INTER_CUBIC);
}


int main(int argc, char** argv)
{
  // Initialize face detection model
  seeta::FaceDetection detector("seeta_fd_frontal_v1.0.bin");
  detector.SetMinFaceSize(40);
  detector.SetScoreThresh(2.f);
  detector.SetImagePyramidScaleFactor(0.8f);
  detector.SetWindowStep(4, 4);

  // Initialize face alignment model 
  seeta::FaceAlignment point_detector("seeta_fa_v1.1.bin");

  //load image
  IplImage *img_grayscale = NULL;
  img_grayscale = cvLoadImage("keliamoniz1.jpg", 0);  ////////////////img_grayscale is imput image///////////////////////
  if (img_grayscale == NULL)
  {
    return 0;
  }

  //IplImage *img_color = cvLoadImage( "image_0001.png",1);
  cv::Mat img_color = cv::imread("keliamoniz1.jpg"); ///////////////img_color is input image///////////////////////
  int pts_num = 5;
  int im_width = img_grayscale->width;
  int im_height = img_grayscale->height;
  unsigned char* data = new unsigned char[im_width * im_height];
  unsigned char* data_ptr = data;
  unsigned char* image_data_ptr = (unsigned char*)img_grayscale->imageData;
  int h = 0;
  for (h = 0; h < im_height; h++) {
    memcpy(data_ptr, image_data_ptr, im_width);
    data_ptr += im_width;
    image_data_ptr += img_grayscale->widthStep;
  }

  seeta::ImageData image_data;
  image_data.data = data;
  image_data.width = im_width;
  image_data.height = im_height;
  image_data.num_channels = 1;

  // Detect faces
  std::vector<seeta::FaceInfo> faces = detector.Detect(image_data);
  int32_t face_num = static_cast<int32_t>(faces.size());

  if (face_num == 0)
  {
    delete[]data;
    cvReleaseImage(&img_grayscale);
    //cvReleaseImage(&img_color);
    img_color.release();
    return 0;
  }

  // Detect 5 facial landmarks
  seeta::FacialLandmark points[5];
  point_detector.PointDetectLandmarks(image_data, faces[0], points);

  // Visualize the results
  cv::rectangle(img_color, cvPoint(faces[0].bbox.x, faces[0].bbox.y), cvPoint(faces[0].bbox.x + faces[0].bbox.width - 1, faces[0].bbox.y + faces[0].bbox.height - 1), CV_RGB(0, 255, 255));
  //cvRectangle(img_color, cvPoint(faces[0].bbox.x, faces[0].bbox.y), cvPoint(faces[0].bbox.x + faces[0].bbox.width - 1, faces[0].bbox.y + faces[0].bbox.height - 1), CV_RGB(255, 0, 0));
  for (int i = 0; i<pts_num; i++)
  {
    cv::circle(img_color, cvPoint(points[i].x, points[i].y), 2, CV_RGB(0, 255, 255), CV_FILLED);
    //cvCircle(img_color, cvPoint(points[i].x, points[i].y), 2, CV_RGB(0, 255, 0), CV_FILLED);
  }

  cv::Mat temp_face;
  cv::Mat temp_align;
    std::vector<float> temp_pts = {(float)points[0].x - faces[0].bbox.x, (float)points[0].y - faces[0].bbox.y,
                                   (float)points[1].x - faces[0].bbox.x ,(float)points[1].y - faces[0].bbox.y,
                                   (float)points[2].x - faces[0].bbox.x ,(float)points[2].y - faces[0].bbox.y,
                                   (float)points[3].x - faces[0].bbox.x, (float)points[3].y - faces[0].bbox.y,
                                   (float)points[4].x - faces[0].bbox.x, (float)points[4].y - faces[0].bbox.y};
  cut(img_color, temp_face, cv::Rect(faces[0].bbox.x, faces[0].bbox.y, faces[0].bbox.width, faces[0].bbox.height));
  rotate(temp_face, temp_align, temp_pts);////////////////////temp_align is output image///////////////////////
  //cvSaveImage("result.jpg", img_color);
  //cv::Mat temp = cv::cvarrToMat(img_color, true);
  //cv::imshow("align", temp_align);
  //cv::imshow("output",img_color);
  //cvWaitKey(0);
  // Release memory
  //cvReleaseImage(&img_color);
  img_color.release();
  cvReleaseImage(&img_grayscale);
  delete[]data;
  return 0;
}
